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