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