JAL-2505 remove setters for SequenceFeature.begin,end,type,featureGroup
[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    * A copy constructor that allows the begin and end positions and group to be
182    * modified
183    * 
184    * @param sf
185    * @param newBegin
186    * @param newEnd
187    * @param newGroup
188    */
189   public SequenceFeature(SequenceFeature sf, int newBegin, int newEnd,
190           String newGroup)
191   {
192     this(sf);
193     begin = newBegin;
194     end = newEnd;
195     featureGroup = newGroup;
196   }
197
198   /**
199    * Two features are considered equal if they have the same type, group,
200    * description, start, end, phase, strand, and (if present) 'Name', ID' and
201    * 'Parent' attributes.
202    * 
203    * Note we need to check Parent to distinguish the same exon occurring in
204    * different transcripts (in Ensembl GFF). This allows assembly of transcript
205    * sequences from their component exon regions.
206    */
207   @Override
208   public boolean equals(Object o)
209   {
210     return equals(o, false);
211   }
212
213   /**
214    * Overloaded method allows the equality test to optionally ignore the
215    * 'Parent' attribute of a feature. This supports avoiding adding many
216    * superficially duplicate 'exon' or CDS features to genomic or protein
217    * sequence.
218    * 
219    * @param o
220    * @param ignoreParent
221    * @return
222    */
223   public boolean equals(Object o, boolean ignoreParent)
224   {
225     if (o == null || !(o instanceof SequenceFeature))
226     {
227       return false;
228     }
229
230     SequenceFeature sf = (SequenceFeature) o;
231     boolean sameScore = Float.isNaN(score) ? Float.isNaN(sf.score)
232             : score == sf.score;
233     if (begin != sf.begin || end != sf.end || !sameScore)
234     {
235       return false;
236     }
237
238     if (getStrand() != sf.getStrand())
239     {
240       return false;
241     }
242
243     if (!(type + description + featureGroup + getPhase()).equals(sf.type
244             + sf.description + sf.featureGroup + sf.getPhase()))
245     {
246       return false;
247     }
248     if (!equalAttribute(getValue("ID"), sf.getValue("ID")))
249     {
250       return false;
251     }
252     if (!equalAttribute(getValue("Name"), sf.getValue("Name")))
253     {
254       return false;
255     }
256     if (!ignoreParent)
257     {
258       if (!equalAttribute(getValue("Parent"), sf.getValue("Parent")))
259       {
260         return false;
261       }
262     }
263     return true;
264   }
265
266   /**
267    * Returns true if both values are null, are both non-null and equal
268    * 
269    * @param att1
270    * @param att2
271    * @return
272    */
273   protected static boolean equalAttribute(Object att1, Object att2)
274   {
275     if (att1 == null && att2 == null)
276     {
277       return true;
278     }
279     if (att1 != null)
280     {
281       return att1.equals(att2);
282     }
283     return att2.equals(att1);
284   }
285
286   /**
287    * DOCUMENT ME!
288    * 
289    * @return DOCUMENT ME!
290    */
291   @Override
292   public int getBegin()
293   {
294     return begin;
295   }
296
297   /**
298    * DOCUMENT ME!
299    * 
300    * @return DOCUMENT ME!
301    */
302   @Override
303   public int getEnd()
304   {
305     return end;
306   }
307
308   /**
309    * DOCUMENT ME!
310    * 
311    * @return DOCUMENT ME!
312    */
313   public String getType()
314   {
315     return type;
316   }
317
318   /**
319    * DOCUMENT ME!
320    * 
321    * @return DOCUMENT ME!
322    */
323   public String getDescription()
324   {
325     return description;
326   }
327
328   public void setDescription(String desc)
329   {
330     description = desc;
331   }
332
333   public String getFeatureGroup()
334   {
335     return featureGroup;
336   }
337
338   public void addLink(String labelLink)
339   {
340     if (links == null)
341     {
342       links = new Vector<String>();
343     }
344
345     if (!links.contains(labelLink))
346     {
347       links.insertElementAt(labelLink, 0);
348     }
349   }
350
351   public float getScore()
352   {
353     return score;
354   }
355
356   public void setScore(float value)
357   {
358     score = value;
359   }
360
361   /**
362    * Used for getting values which are not in the basic set. eg STRAND, PHASE
363    * for GFF file
364    * 
365    * @param key
366    *          String
367    */
368   public Object getValue(String key)
369   {
370     if (otherDetails == null)
371     {
372       return null;
373     }
374     else
375     {
376       return otherDetails.get(key);
377     }
378   }
379
380   /**
381    * Returns a property value for the given key if known, else the specified
382    * default value
383    * 
384    * @param key
385    * @param defaultValue
386    * @return
387    */
388   public Object getValue(String key, Object defaultValue)
389   {
390     Object value = getValue(key);
391     return value == null ? defaultValue : value;
392   }
393
394   /**
395    * Used for setting values which are not in the basic set. eg STRAND, FRAME
396    * for GFF file
397    * 
398    * @param key
399    *          eg STRAND
400    * @param value
401    *          eg +
402    */
403   public void setValue(String key, Object value)
404   {
405     if (value != null)
406     {
407       if (otherDetails == null)
408       {
409         otherDetails = new HashMap<String, Object>();
410       }
411
412       otherDetails.put(key, value);
413     }
414   }
415
416   /*
417    * The following methods are added to maintain the castor Uniprot mapping file
418    * for the moment.
419    */
420   public void setStatus(String status)
421   {
422     setValue(STATUS, status);
423   }
424
425   public String getStatus()
426   {
427     return (String) getValue(STATUS);
428   }
429
430   public void setAttributes(String attr)
431   {
432     setValue(ATTRIBUTES, attr);
433   }
434
435   public String getAttributes()
436   {
437     return (String) getValue(ATTRIBUTES);
438   }
439
440   public void setPosition(int pos)
441   {
442     begin = pos;
443     end = pos;
444   }
445
446   public int getPosition()
447   {
448     return begin;
449   }
450
451   /**
452    * Return 1 for forward strand ('+' in GFF), -1 for reverse strand ('-' in
453    * GFF), and 0 for unknown or not (validly) specified
454    * 
455    * @return
456    */
457   public int getStrand()
458   {
459     int strand = 0;
460     if (otherDetails != null)
461     {
462       Object str = otherDetails.get(STRAND);
463       if ("-".equals(str))
464       {
465         strand = -1;
466       }
467       else if ("+".equals(str))
468       {
469         strand = 1;
470       }
471     }
472     return strand;
473   }
474
475   /**
476    * Set the value of strand
477    * 
478    * @param strand
479    *          should be "+" for forward, or "-" for reverse
480    */
481   public void setStrand(String strand)
482   {
483     setValue(STRAND, strand);
484   }
485
486   public void setPhase(String phase)
487   {
488     setValue(PHASE, phase);
489   }
490
491   public String getPhase()
492   {
493     return (String) getValue(PHASE);
494   }
495
496   /**
497    * Sets the 'raw' ENA format location specifier e.g. join(12..45,89..121)
498    * 
499    * @param loc
500    */
501   public void setEnaLocation(String loc)
502   {
503     setValue(LOCATION, loc);
504   }
505
506   /**
507    * Gets the 'raw' ENA format location specifier e.g. join(12..45,89..121)
508    * 
509    * @param loc
510    */
511   public String getEnaLocation()
512   {
513     return (String) getValue(LOCATION);
514   }
515
516   /**
517    * Readable representation, for debug only, not guaranteed not to change
518    * between versions
519    */
520   @Override
521   public String toString()
522   {
523     return String.format("%d %d %s %s", getBegin(), getEnd(), getType(),
524             getDescription());
525   }
526
527   /**
528    * Overridden to ensure that whenever two objects are equal, they have the
529    * same hashCode
530    */
531   @Override
532   public int hashCode()
533   {
534     String s = getType() + getDescription() + getFeatureGroup()
535             + getValue("ID") + getValue("Name") + getValue("Parent")
536             + getPhase();
537     return s.hashCode() + getBegin() + getEnd() + (int) getScore()
538             + getStrand();
539   }
540
541   /**
542    * Answers true if the feature's start/end values represent two related
543    * positions, rather than ends of a range. Such features may be visualised or
544    * reported differently to features on a range.
545    */
546   @Override
547   public boolean isContactFeature()
548   {
549     // TODO abstract one day to a FeatureType class
550     if ("disulfide bond".equalsIgnoreCase(type)
551             || "disulphide bond".equalsIgnoreCase(type))
552     {
553       return true;
554     }
555     return false;
556   }
557
558   /**
559    * Answers true if the sequence has zero start and end position
560    * 
561    * @return
562    */
563   public boolean isNonPositional()
564   {
565     return begin == 0 && end == 0;
566   }
567 }