2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
21 package jalview.datamodel;
23 import jalview.datamodel.features.FeatureLocationI;
25 import java.util.HashMap;
27 import java.util.Map.Entry;
28 import java.util.TreeMap;
29 import java.util.Vector;
37 public class SequenceFeature implements FeatureLocationI
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
43 private static final float NO_SCORE = 0f;
45 private static final String STATUS = "status";
47 private static final String STRAND = "STRAND";
49 // private key for Phase designed not to conflict with real GFF data
50 private static final String PHASE = "!Phase";
52 // private key for ENA location designed not to conflict with real GFF data
53 private static final String LOCATION = "!Location";
56 * map of otherDetails special keys, and their value fields' delimiter
58 private static final Map<String, String> INFO_KEYS = new HashMap<>();
62 INFO_KEYS.put("CSQ", ",");
66 * ATTRIBUTES is reserved for the GFF 'column 9' data, formatted as
67 * name1=value1;name2=value2,value3;...etc
69 private static final String ATTRIBUTES = "ATTRIBUTES";
72 * type, begin, end, featureGroup, score and contactFeature are final
73 * to ensure that the integrity of SequenceFeatures data store
74 * can't be broken by direct update of these fields
76 public final String type;
78 public final int begin;
82 public final String featureGroup;
84 public final float score;
86 private final boolean contactFeature;
88 public String description;
91 * a map of key-value pairs; may be populated from GFF 'column 9' data,
92 * other data sources (e.g. GenBank file), or programmatically
94 public Map<String, Object> otherDetails;
96 public Vector<String> links;
99 * Constructs a duplicate feature. Note: Uses makes a shallow copy of the
100 * otherDetails map, so the new and original SequenceFeature may reference the
101 * same objects in the map.
105 public SequenceFeature(SequenceFeature cpy)
107 this(cpy, cpy.getBegin(), cpy.getEnd(), cpy.getFeatureGroup(), cpy
120 public SequenceFeature(String theType, String theDesc, int theBegin,
121 int theEnd, String group)
123 this(theType, theDesc, theBegin, theEnd, NO_SCORE, group);
127 * Constructor including a score value
136 public SequenceFeature(String theType, String theDesc, int theBegin,
137 int theEnd, float theScore, String group)
140 this.description = theDesc;
141 this.begin = theBegin;
143 this.featureGroup = group;
144 this.score = theScore;
147 * for now, only "Disulfide/disulphide bond" is treated as a contact feature
149 this.contactFeature = "disulfide bond".equalsIgnoreCase(type)
150 || "disulphide bond".equalsIgnoreCase(type);
154 * A copy constructor that allows the value of final fields to be 'modified'
163 public SequenceFeature(SequenceFeature sf, String newType, int newBegin,
164 int newEnd, String newGroup, float newScore)
166 this(newType, sf.getDescription(), newBegin, newEnd, newScore,
169 if (sf.otherDetails != null)
171 otherDetails = new HashMap<String, Object>();
172 for (Entry<String, Object> entry : sf.otherDetails.entrySet())
174 otherDetails.put(entry.getKey(), entry.getValue());
177 if (sf.links != null && sf.links.size() > 0)
179 links = new Vector<String>();
180 for (int i = 0, iSize = sf.links.size(); i < iSize; i++)
182 links.addElement(sf.links.elementAt(i));
188 * A copy constructor that allows the value of final fields to be 'modified'
196 public SequenceFeature(SequenceFeature sf, int newBegin, int newEnd,
197 String newGroup, float newScore)
199 this(sf, sf.getType(), newBegin, newEnd, newGroup, newScore);
203 * Two features are considered equal if they have the same type, group,
204 * description, start, end, phase, strand, and (if present) 'Name', ID' and
205 * 'Parent' attributes.
207 * Note we need to check Parent to distinguish the same exon occurring in
208 * different transcripts (in Ensembl GFF). This allows assembly of transcript
209 * sequences from their component exon regions.
212 public boolean equals(Object o)
214 return equals(o, false);
218 * Overloaded method allows the equality test to optionally ignore the
219 * 'Parent' attribute of a feature. This supports avoiding adding many
220 * superficially duplicate 'exon' or CDS features to genomic or protein
224 * @param ignoreParent
227 public boolean equals(Object o, boolean ignoreParent)
229 if (o == null || !(o instanceof SequenceFeature))
234 SequenceFeature sf = (SequenceFeature) o;
235 boolean sameScore = Float.isNaN(score) ? Float.isNaN(sf.score)
237 if (begin != sf.begin || end != sf.end || !sameScore)
242 if (getStrand() != sf.getStrand())
247 if (!(type + description + featureGroup + getPhase()).equals(
248 sf.type + sf.description + sf.featureGroup + sf.getPhase()))
252 if (!equalAttribute(getValue("ID"), sf.getValue("ID")))
256 if (!equalAttribute(getValue("Name"), sf.getValue("Name")))
262 if (!equalAttribute(getValue("Parent"), sf.getValue("Parent")))
271 * Returns true if both values are null, are both non-null and equal
277 protected static boolean equalAttribute(Object att1, Object att2)
279 if (att1 == null && att2 == null)
285 return att1.equals(att2);
287 return att2.equals(att1);
293 * @return DOCUMENT ME!
296 public int getBegin()
304 * @return DOCUMENT ME!
315 * @return DOCUMENT ME!
317 public String getType()
325 * @return DOCUMENT ME!
327 public String getDescription()
332 public void setDescription(String desc)
337 public String getFeatureGroup()
342 public void addLink(String labelLink)
346 links = new Vector<String>();
349 if (!links.contains(labelLink))
351 links.insertElementAt(labelLink, 0);
355 public float getScore()
361 * Used for getting values which are not in the basic set. eg STRAND, PHASE
367 public Object getValue(String key)
369 if (otherDetails == null)
375 return otherDetails.get(key);
380 * Returns a property value for the given key if known, else the specified
384 * @param defaultValue
387 public Object getValue(String key, Object defaultValue)
389 Object value = getValue(key);
390 return value == null ? defaultValue : value;
394 * Used for setting values which are not in the basic set. eg STRAND, FRAME
402 public void setValue(String key, Object value)
406 if (otherDetails == null)
408 otherDetails = new HashMap<String, Object>();
411 otherDetails.put(key, value);
416 * The following methods are added to maintain the castor Uniprot mapping file
419 public void setStatus(String status)
421 setValue(STATUS, status);
424 public String getStatus()
426 return (String) getValue(STATUS);
429 public void setAttributes(String attr)
431 setValue(ATTRIBUTES, attr);
434 public String getAttributes()
436 return (String) getValue(ATTRIBUTES);
440 * Return 1 for forward strand ('+' in GFF), -1 for reverse strand ('-' in
441 * GFF), and 0 for unknown or not (validly) specified
445 public int getStrand()
448 if (otherDetails != null)
450 Object str = otherDetails.get(STRAND);
455 else if ("+".equals(str))
464 * Set the value of strand
467 * should be "+" for forward, or "-" for reverse
469 public void setStrand(String strand)
471 setValue(STRAND, strand);
474 public void setPhase(String phase)
476 setValue(PHASE, phase);
479 public String getPhase()
481 return (String) getValue(PHASE);
485 * Sets the 'raw' ENA format location specifier e.g. join(12..45,89..121)
489 public void setEnaLocation(String loc)
491 setValue(LOCATION, loc);
495 * Gets the 'raw' ENA format location specifier e.g. join(12..45,89..121)
499 public String getEnaLocation()
501 return (String) getValue(LOCATION);
505 * Readable representation, for debug only, not guaranteed not to change
509 public String toString()
511 return String.format("%d %d %s %s", getBegin(), getEnd(), getType(),
516 * Overridden to ensure that whenever two objects are equal, they have the
520 public int hashCode()
522 String s = getType() + getDescription() + getFeatureGroup()
523 + getValue("ID") + getValue("Name") + getValue("Parent")
525 return s.hashCode() + getBegin() + getEnd() + (int) getScore()
530 * Answers true if the feature's start/end values represent two related
531 * positions, rather than ends of a range. Such features may be visualised or
532 * reported differently to features on a range.
535 public boolean isContactFeature()
537 return contactFeature;
541 * Answers true if the sequence has zero start and end position
545 public boolean isNonPositional()
547 return begin == 0 && end == 0;
551 * Answers a formatted text report of feature details
555 public String getDetailsReport()
557 StringBuilder sb = new StringBuilder(128);
560 sb.append(String.format("%s %d %s", type, begin, description));
564 sb.append(String.format("%s %d-%d %s", type, begin, end, description));
566 if (featureGroup != null)
568 sb.append(" (").append(featureGroup).append(")");
572 if (otherDetails != null)
574 TreeMap<String, Object> ordered = new TreeMap<>(
575 String.CASE_INSENSITIVE_ORDER);
576 ordered.putAll(otherDetails);
578 for (Entry<String, Object> entry : ordered.entrySet())
580 String key = entry.getKey();
581 if (ATTRIBUTES.equals(key))
583 continue; // to avoid double reporting
585 if (INFO_KEYS.containsKey(key))
588 * split selected INFO data by delimiter over multiple lines
590 sb.append(key).append("=\n ");
591 String delimiter = INFO_KEYS.get(key);
592 String value = entry.getValue().toString();
593 sb.append(value.replace(delimiter, "\n "));
597 sb.append(key + "=" + entry.getValue().toString() + "\n");
601 String text = sb.toString();