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