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