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