JAL-2041 add ID,clinical_significance to feature attributes (for export)
[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   /*
43    * ATTRIBUTES is reserved for the GFF 'column 9' data, formatted as
44    * name1=value1;name2=value2,value3;...etc
45    */
46   private static final String ATTRIBUTES = "ATTRIBUTES";
47
48   public int begin;
49
50   public int end;
51
52   public float score;
53
54   public String type;
55
56   public String description;
57
58   public Map<String, Object> otherDetails;
59
60   public Vector<String> links;
61
62   // Feature group can be set from a features file
63   // as a group of features between STARTGROUP and ENDGROUP markers
64   public String featureGroup;
65
66   public SequenceFeature()
67   {
68   }
69
70   /**
71    * Constructs a duplicate feature. Note: Uses makes a shallow copy of the
72    * otherDetails map, so the new and original SequenceFeature may reference the
73    * same objects in the map.
74    * 
75    * @param cpy
76    */
77   public SequenceFeature(SequenceFeature cpy)
78   {
79     if (cpy != null)
80     {
81       begin = cpy.begin;
82       end = cpy.end;
83       score = cpy.score;
84       if (cpy.type != null)
85       {
86         type = new String(cpy.type);
87       }
88       if (cpy.description != null)
89       {
90         description = new String(cpy.description);
91       }
92       if (cpy.featureGroup != null)
93       {
94         featureGroup = new String(cpy.featureGroup);
95       }
96       if (cpy.otherDetails != null)
97       {
98         try
99         {
100           otherDetails = (Map<String, Object>) ((HashMap<String, Object>) cpy.otherDetails)
101                   .clone();
102         } catch (Exception e)
103         {
104           // ignore
105         }
106       }
107       if (cpy.links != null && cpy.links.size() > 0)
108       {
109         links = new Vector<String>();
110         for (int i = 0, iSize = cpy.links.size(); i < iSize; i++)
111         {
112           links.addElement(cpy.links.elementAt(i));
113         }
114       }
115     }
116   }
117
118   /**
119    * Constructor including a Status value
120    * 
121    * @param type
122    * @param desc
123    * @param status
124    * @param begin
125    * @param end
126    * @param featureGroup
127    */
128   public SequenceFeature(String type, String desc, String status,
129           int begin, int end, String featureGroup)
130   {
131     this(type, desc, begin, end, featureGroup);
132     setStatus(status);
133   }
134
135   /**
136    * Constructor
137    * 
138    * @param type
139    * @param desc
140    * @param begin
141    * @param end
142    * @param featureGroup
143    */
144   SequenceFeature(String type, String desc, int begin, int end,
145           String featureGroup)
146   {
147     this.type = type;
148     this.description = desc;
149     this.begin = begin;
150     this.end = end;
151     this.featureGroup = featureGroup;
152   }
153
154   /**
155    * Constructor including a score value
156    * 
157    * @param type
158    * @param desc
159    * @param begin
160    * @param end
161    * @param score
162    * @param featureGroup
163    */
164   public SequenceFeature(String type, String desc, int begin, int end,
165           float score, String featureGroup)
166   {
167     this(type, desc, begin, end, featureGroup);
168     this.score = score;
169   }
170
171   /**
172    * Two features are considered equal if they have the same type, group,
173    * description, start, end, phase, strand, and (if present) 'Name', ID' and
174    * 'Parent' attributes.
175    * 
176    * Note we need to check Parent to distinguish the same exon occurring in
177    * different transcripts (in Ensembl GFF). This allows assembly of transcript
178    * sequences from their component exon regions.
179    */
180   @Override
181   public boolean equals(Object o)
182   {
183     return equals(o, false);
184   }
185
186   /**
187    * Overloaded method allows the equality test to optionally ignore the
188    * 'Parent' attribute of a feature. This supports avoiding adding many
189    * superficially duplicate 'exon' or CDS features to genomic or protein
190    * sequence.
191    * 
192    * @param o
193    * @param ignoreParent
194    * @return
195    */
196   public boolean equals(Object o, boolean ignoreParent)
197   {
198     if (o == null || !(o instanceof SequenceFeature))
199     {
200       return false;
201     }
202
203     SequenceFeature sf = (SequenceFeature) o;
204     if (begin != sf.begin || end != sf.end || score != sf.score)
205     {
206       return false;
207     }
208
209     if (getStrand() != sf.getStrand())
210     {
211       return false;
212     }
213
214     if (!(type + description + featureGroup + getPhase()).equals(sf.type
215             + sf.description + sf.featureGroup + sf.getPhase()))
216     {
217       return false;
218     }
219     if (!equalAttribute(getValue("ID"), sf.getValue("ID")))
220     {
221       return false;
222     }
223     if (!equalAttribute(getValue("Name"), sf.getValue("Name")))
224     {
225       return false;
226     }
227     if (!ignoreParent)
228     {
229       if (!equalAttribute(getValue("Parent"), sf.getValue("Parent")))
230       {
231         return false;
232       }
233     }
234     return true;
235   }
236
237   /**
238    * Returns true if both values are null, are both non-null and equal
239    * 
240    * @param att1
241    * @param att2
242    * @return
243    */
244   protected static boolean equalAttribute(Object att1, Object att2)
245   {
246     if (att1 == null && att2 == null)
247     {
248       return true;
249     }
250     if (att1 != null)
251     {
252       return att1.equals(att2);
253     }
254     return att2.equals(att1);
255   }
256
257   /**
258    * DOCUMENT ME!
259    * 
260    * @return DOCUMENT ME!
261    */
262   public int getBegin()
263   {
264     return begin;
265   }
266
267   public void setBegin(int start)
268   {
269     this.begin = start;
270   }
271
272   /**
273    * DOCUMENT ME!
274    * 
275    * @return DOCUMENT ME!
276    */
277   public int getEnd()
278   {
279     return end;
280   }
281
282   public void setEnd(int end)
283   {
284     this.end = end;
285   }
286
287   /**
288    * DOCUMENT ME!
289    * 
290    * @return DOCUMENT ME!
291    */
292   public String getType()
293   {
294     return type;
295   }
296
297   public void setType(String type)
298   {
299     this.type = type;
300   }
301
302   /**
303    * DOCUMENT ME!
304    * 
305    * @return DOCUMENT ME!
306    */
307   public String getDescription()
308   {
309     return description;
310   }
311
312   public void setDescription(String desc)
313   {
314     description = desc;
315   }
316
317   public String getFeatureGroup()
318   {
319     return featureGroup;
320   }
321
322   public void setFeatureGroup(String featureGroup)
323   {
324     this.featureGroup = featureGroup;
325   }
326
327   public void addLink(String labelLink)
328   {
329     if (links == null)
330     {
331       links = new Vector<String>();
332     }
333
334     links.insertElementAt(labelLink, 0);
335   }
336
337   public float getScore()
338   {
339     return score;
340   }
341
342   public void setScore(float value)
343   {
344     score = value;
345   }
346
347   /**
348    * Used for getting values which are not in the basic set. eg STRAND, PHASE
349    * for GFF file
350    * 
351    * @param key
352    *          String
353    */
354   public Object getValue(String key)
355   {
356     if (otherDetails == null)
357     {
358       return null;
359     }
360     else
361     {
362       return otherDetails.get(key);
363     }
364   }
365
366   /**
367    * Returns a property value for the given key if known, else the specified
368    * default value
369    * 
370    * @param key
371    * @param defaultValue
372    * @return
373    */
374   public Object getValue(String key, Object defaultValue)
375   {
376     Object value = getValue(key);
377     return value == null ? defaultValue : value;
378   }
379
380   /**
381    * Used for setting values which are not in the basic set. eg STRAND, FRAME
382    * for GFF file
383    * 
384    * @param key
385    *          eg STRAND
386    * @param value
387    *          eg +
388    */
389   public void setValue(String key, Object value)
390   {
391     if (value != null)
392     {
393       if (otherDetails == null)
394       {
395         otherDetails = new HashMap<String, Object>();
396       }
397
398       otherDetails.put(key, value);
399     }
400   }
401
402   /*
403    * The following methods are added to maintain the castor Uniprot mapping file
404    * for the moment.
405    */
406   public void setStatus(String status)
407   {
408     setValue(STATUS, status);
409   }
410
411   public String getStatus()
412   {
413     return (String) getValue(STATUS);
414   }
415
416   public void setAttributes(String attr)
417   {
418     setValue(ATTRIBUTES, attr);
419   }
420
421   public String getAttributes()
422   {
423     return (String) getValue(ATTRIBUTES);
424   }
425
426   public void setPosition(int pos)
427   {
428     begin = pos;
429     end = pos;
430   }
431
432   public int getPosition()
433   {
434     return begin;
435   }
436
437   /**
438    * Return 1 for forward strand ('+' in GFF), -1 for reverse strand ('-' in
439    * GFF), and 0 for unknown or not (validly) specified
440    * 
441    * @return
442    */
443   public int getStrand()
444   {
445     int strand = 0;
446     if (otherDetails != null)
447     {
448       Object str = otherDetails.get(STRAND);
449       if ("-".equals(str))
450       {
451         strand = -1;
452       }
453       else if ("+".equals(str))
454       {
455         strand = 1;
456       }
457     }
458     return strand;
459   }
460
461   /**
462    * Set the value of strand
463    * 
464    * @param strand
465    *          should be "+" for forward, or "-" for reverse
466    */
467   public void setStrand(String strand)
468   {
469     setValue(STRAND, strand);
470   }
471
472   public void setPhase(String phase)
473   {
474     setValue(PHASE, phase);
475   }
476
477   public String getPhase()
478   {
479     return (String) getValue(PHASE);
480   }
481
482   /**
483    * Readable representation, for debug only, not guaranteed not to change
484    * between versions
485    */
486   @Override
487   public String toString()
488   {
489     return String.format("%d %d %s %s", getBegin(), getEnd(), getType(),
490             getDescription());
491   }
492
493   /**
494    * Overridden to ensure that whenever two objects are equal, they have the
495    * same hashCode
496    */
497   @Override
498   public int hashCode()
499   {
500     String s = getType() + getDescription() + getFeatureGroup()
501             + getValue("ID") + getValue("Name") + getValue("Parent")
502             + getPhase();
503     return s.hashCode() + getBegin() + getEnd() + (int) getScore()
504             + getStrand();
505   }
506 }