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