Clover icon

jalviewX

  1. Project Clover database Wed Oct 31 2018 15:13:58 GMT
  2. Package jalview.datamodel

File SequenceFeature.java

 

Coverage histogram

../../img/srcFileCovDistChart9.png
12% of files have more coverage

Code metrics

82
144
38
1
738
408
92
0.64
3.79
38
2.42

Classes

Class Line # Actions
SequenceFeature 42 144 92 40
0.848484984.8%
 

Contributing tests

This file is covered by 204 tests. .

Source view

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.FeatureAttributeType;
24    import jalview.datamodel.features.FeatureAttributes;
25    import jalview.datamodel.features.FeatureLocationI;
26    import jalview.datamodel.features.FeatureSourceI;
27    import jalview.datamodel.features.FeatureSources;
28    import jalview.util.StringUtils;
29   
30    import java.util.HashMap;
31    import java.util.Map;
32    import java.util.Map.Entry;
33    import java.util.SortedMap;
34    import java.util.TreeMap;
35    import java.util.Vector;
36   
37    /**
38    * A class that models a single contiguous feature on a sequence. If flag
39    * 'contactFeature' is true, the start and end positions are interpreted instead
40    * as two contact points.
41    */
 
42    public class SequenceFeature implements FeatureLocationI
43    {
44    /*
45    * score value if none is set; preferably Float.Nan, but see
46    * JAL-2060 and JAL-2554 for a couple of blockers to that
47    */
48    private static final float NO_SCORE = 0f;
49   
50    private static final String STATUS = "status";
51   
52    private static final String STRAND = "STRAND";
53   
54    // private key for Phase designed not to conflict with real GFF data
55    private static final String PHASE = "!Phase";
56   
57    // private key for ENA location designed not to conflict with real GFF data
58    private static final String LOCATION = "!Location";
59   
60    private static final String ROW_DATA = "<tr><td>%s</td><td>%s</td><td>%s</td></tr>";
61   
62    /*
63    * ATTRIBUTES is reserved for the GFF 'column 9' data, formatted as
64    * name1=value1;name2=value2,value3;...etc
65    */
66    private static final String ATTRIBUTES = "ATTRIBUTES";
67   
68    /*
69    * type, begin, end, featureGroup, score and contactFeature are final
70    * to ensure that the integrity of SequenceFeatures data store
71    * can't be broken by direct update of these fields
72    */
73    public final String type;
74   
75    public final int begin;
76   
77    public final int end;
78   
79    public final String featureGroup;
80   
81    public final float score;
82   
83    private final boolean contactFeature;
84   
85    public String description;
86   
87    /*
88    * a map of key-value pairs; may be populated from GFF 'column 9' data,
89    * other data sources (e.g. GenBank file), or programmatically
90    */
91    public Map<String, Object> otherDetails;
92   
93    public Vector<String> links;
94   
95    /*
96    * the identifier (if known) for the FeatureSource held in FeatureSources,
97    * as a provider of metadata about feature attributes
98    */
99    private String source;
100   
101    /**
102    * Constructs a duplicate feature. Note: Uses makes a shallow copy of the
103    * otherDetails map, so the new and original SequenceFeature may reference the
104    * same objects in the map.
105    *
106    * @param cpy
107    */
 
108  81 toggle public SequenceFeature(SequenceFeature cpy)
109    {
110  81 this(cpy, cpy.getBegin(), cpy.getEnd(), cpy.getFeatureGroup(), cpy
111    .getScore());
112    }
113   
114    /**
115    * Constructor
116    *
117    * @param theType
118    * @param theDesc
119    * @param theBegin
120    * @param theEnd
121    * @param group
122    */
 
123  41466 toggle public SequenceFeature(String theType, String theDesc, int theBegin,
124    int theEnd, String group)
125    {
126  41466 this(theType, theDesc, theBegin, theEnd, NO_SCORE, group);
127    }
128   
129    /**
130    * Constructor including a score value
131    *
132    * @param theType
133    * @param theDesc
134    * @param theBegin
135    * @param theEnd
136    * @param theScore
137    * @param group
138    */
 
139  67376 toggle public SequenceFeature(String theType, String theDesc, int theBegin,
140    int theEnd, float theScore, String group)
141    {
142  67376 this.type = theType;
143  67376 this.description = theDesc;
144  67376 this.begin = theBegin;
145  67376 this.end = theEnd;
146  67376 this.featureGroup = group;
147  67376 this.score = theScore;
148   
149    /*
150    * for now, only "Disulfide/disulphide bond" is treated as a contact feature
151    */
152  67376 this.contactFeature = "disulfide bond".equalsIgnoreCase(type)
153    || "disulphide bond".equalsIgnoreCase(type);
154    }
155   
156    /**
157    * A copy constructor that allows the value of final fields to be 'modified'
158    *
159    * @param sf
160    * @param newType
161    * @param newBegin
162    * @param newEnd
163    * @param newGroup
164    * @param newScore
165    */
 
166  15301 toggle public SequenceFeature(SequenceFeature sf, String newType, int newBegin,
167    int newEnd, String newGroup, float newScore)
168    {
169  15301 this(newType, sf.getDescription(), newBegin, newEnd, newScore,
170    newGroup);
171   
172  15301 this.source = sf.source;
173   
174  15301 if (sf.otherDetails != null)
175    {
176  98 otherDetails = new HashMap<>();
177  98 for (Entry<String, Object> entry : sf.otherDetails.entrySet())
178    {
179  240 otherDetails.put(entry.getKey(), entry.getValue());
180    }
181    }
182  15301 if (sf.links != null && sf.links.size() > 0)
183    {
184  0 links = new Vector<>();
185  0 for (int i = 0, iSize = sf.links.size(); i < iSize; i++)
186    {
187  0 links.addElement(sf.links.elementAt(i));
188    }
189    }
190    }
191   
192    /**
193    * A copy constructor that allows the value of final fields to be 'modified'
194    *
195    * @param sf
196    * @param newBegin
197    * @param newEnd
198    * @param newGroup
199    * @param newScore
200    */
 
201  15300 toggle public SequenceFeature(SequenceFeature sf, int newBegin, int newEnd,
202    String newGroup, float newScore)
203    {
204  15300 this(sf, sf.getType(), newBegin, newEnd, newGroup, newScore);
205    }
206   
207    /**
208    * Two features are considered equal if they have the same type, group,
209    * description, start, end, phase, strand, and (if present) 'Name', ID' and
210    * 'Parent' attributes.
211    *
212    * Note we need to check Parent to distinguish the same exon occurring in
213    * different transcripts (in Ensembl GFF). This allows assembly of transcript
214    * sequences from their component exon regions.
215    */
 
216  69125 toggle @Override
217    public boolean equals(Object o)
218    {
219  69125 return equals(o, false);
220    }
221   
222    /**
223    * Overloaded method allows the equality test to optionally ignore the
224    * 'Parent' attribute of a feature. This supports avoiding adding many
225    * superficially duplicate 'exon' or CDS features to genomic or protein
226    * sequence.
227    *
228    * @param o
229    * @param ignoreParent
230    * @return
231    */
 
232  69125 toggle public boolean equals(Object o, boolean ignoreParent)
233    {
234  69125 if (o == null || !(o instanceof SequenceFeature))
235    {
236  1 return false;
237    }
238   
239  69124 SequenceFeature sf = (SequenceFeature) o;
240  69124 boolean sameScore = Float.isNaN(score) ? Float.isNaN(sf.score)
241    : score == sf.score;
242  69124 if (begin != sf.begin || end != sf.end || !sameScore)
243    {
244  50228 return false;
245    }
246   
247  18896 if (getStrand() != sf.getStrand())
248    {
249  1 return false;
250    }
251   
252  18895 if (!(type + description + featureGroup + getPhase()).equals(
253    sf.type + sf.description + sf.featureGroup + sf.getPhase()))
254    {
255  108 return false;
256    }
257  18787 if (!equalAttribute(getValue("ID"), sf.getValue("ID")))
258    {
259  1 return false;
260    }
261  18786 if (!equalAttribute(getValue("Name"), sf.getValue("Name")))
262    {
263  1 return false;
264    }
265  18785 if (!ignoreParent)
266    {
267  18785 if (!equalAttribute(getValue("Parent"), sf.getValue("Parent")))
268    {
269  1 return false;
270    }
271    }
272  18784 return true;
273    }
274   
275    /**
276    * Returns true if both values are null, are both non-null and equal
277    *
278    * @param att1
279    * @param att2
280    * @return
281    */
 
282  56358 toggle protected static boolean equalAttribute(Object att1, Object att2)
283    {
284  56358 if (att1 == null && att2 == null)
285    {
286  56331 return true;
287    }
288  27 if (att1 != null)
289    {
290  27 return att1.equals(att2);
291    }
292  0 return att2.equals(att1);
293    }
294   
295    /**
296    * DOCUMENT ME!
297    *
298    * @return DOCUMENT ME!
299    */
 
300  2274953 toggle @Override
301    public int getBegin()
302    {
303  2274957 return begin;
304    }
305   
306    /**
307    * DOCUMENT ME!
308    *
309    * @return DOCUMENT ME!
310    */
 
311  1188933 toggle @Override
312    public int getEnd()
313    {
314  1188942 return end;
315    }
316   
317    /**
318    * DOCUMENT ME!
319    *
320    * @return DOCUMENT ME!
321    */
 
322  157786 toggle public String getType()
323    {
324  157788 return type;
325    }
326   
327    /**
328    * DOCUMENT ME!
329    *
330    * @return DOCUMENT ME!
331    */
 
332  31289 toggle public String getDescription()
333    {
334  31289 return description;
335    }
336   
 
337  340 toggle public void setDescription(String desc)
338    {
339  340 description = desc;
340    }
341   
 
342  104158 toggle public String getFeatureGroup()
343    {
344  104158 return featureGroup;
345    }
346   
 
347  2452 toggle public void addLink(String labelLink)
348    {
349  2452 if (links == null)
350    {
351  2452 links = new Vector<>();
352    }
353   
354  2452 if (!links.contains(labelLink))
355    {
356  2452 links.insertElementAt(labelLink, 0);
357    }
358    }
359   
 
360  58181 toggle public float getScore()
361    {
362  58181 return score;
363    }
364   
365    /**
366    * Used for getting values which are not in the basic set. eg STRAND, PHASE
367    * for GFF file
368    *
369    * @param key
370    * String
371    */
 
372  164465 toggle public Object getValue(String key)
373    {
374  164465 if (otherDetails == null)
375    {
376  62922 return null;
377    }
378    else
379    {
380  101543 return otherDetails.get(key);
381    }
382    }
383   
384    /**
385    * Answers the value of the specified attribute as string, or null if no such
386    * value. If more than one attribute name is provided, tries to resolve as keys
387    * to nested maps. For example, if attribute "CSQ" holds a map of key-value
388    * pairs, then getValueAsString("CSQ", "Allele") returns the value of "Allele"
389    * in that map.
390    *
391    * @param key
392    * @return
393    */
 
394  83 toggle public String getValueAsString(String... key)
395    {
396  83 if (otherDetails == null)
397    {
398  7 return null;
399    }
400  76 Object value = otherDetails.get(key[0]);
401  76 if (key.length > 1 && value instanceof Map<?, ?>)
402    {
403  20 value = ((Map) value).get(key[1]);
404    }
405  76 return value == null ? null : value.toString();
406    }
407   
408    /**
409    * Returns a property value for the given key if known, else the specified
410    * default value
411    *
412    * @param key
413    * @param defaultValue
414    * @return
415    */
 
416  2 toggle public Object getValue(String key, Object defaultValue)
417    {
418  2 Object value = getValue(key);
419  2 return value == null ? defaultValue : value;
420    }
421   
422    /**
423    * Used for setting values which are not in the basic set. eg STRAND, FRAME
424    * for GFF file
425    *
426    * @param key
427    * eg STRAND
428    * @param value
429    * eg +
430    */
 
431  26762 toggle public void setValue(String key, Object value)
432    {
433  26762 if (value != null)
434    {
435  16882 if (otherDetails == null)
436    {
437  16516 otherDetails = new HashMap<>();
438    }
439   
440  16882 otherDetails.put(key, value);
441  16882 recordAttribute(key, value);
442    }
443    }
444   
445    /**
446    * Notifies the addition of a feature attribute. This lets us keep track of
447    * which attributes are present on each feature type, and also the range of
448    * numerical-valued attributes.
449    *
450    * @param key
451    * @param value
452    */
 
453  16882 toggle protected void recordAttribute(String key, Object value)
454    {
455  16882 String attDesc = null;
456  16882 if (source != null)
457    {
458  133 attDesc = FeatureSources.getInstance().getSource(source)
459    .getAttributeName(key);
460    }
461   
462  16882 FeatureAttributes.getInstance().addAttribute(this.type, attDesc, value,
463    key);
464    }
465   
466    /*
467    * The following methods are added to maintain the castor Uniprot mapping file
468    * for the moment.
469    */
 
470  23472 toggle public void setStatus(String status)
471    {
472  23472 setValue(STATUS, status);
473    }
474   
 
475  13589 toggle public String getStatus()
476    {
477  13589 return (String) getValue(STATUS);
478    }
479   
 
480  30 toggle public void setAttributes(String attr)
481    {
482  30 setValue(ATTRIBUTES, attr);
483    }
484   
 
485  17 toggle public String getAttributes()
486    {
487  17 return (String) getValue(ATTRIBUTES);
488    }
489   
490    /**
491    * Return 1 for forward strand ('+' in GFF), -1 for reverse strand ('-' in
492    * GFF), and 0 for unknown or not (validly) specified
493    *
494    * @return
495    */
 
496  37821 toggle public int getStrand()
497    {
498  37821 int strand = 0;
499  37821 if (otherDetails != null)
500    {
501  25388 Object str = otherDetails.get(STRAND);
502  25388 if ("-".equals(str))
503    {
504  5 strand = -1;
505    }
506  25383 else if ("+".equals(str))
507    {
508  42 strand = 1;
509    }
510    }
511  37821 return strand;
512    }
513   
514    /**
515    * Set the value of strand
516    *
517    * @param strand
518    * should be "+" for forward, or "-" for reverse
519    */
 
520  51 toggle public void setStrand(String strand)
521    {
522  51 setValue(STRAND, strand);
523    }
524   
 
525  35 toggle public void setPhase(String phase)
526    {
527  35 setValue(PHASE, phase);
528    }
529   
 
530  37821 toggle public String getPhase()
531    {
532  37821 return (String) getValue(PHASE);
533    }
534   
535    /**
536    * Sets the 'raw' ENA format location specifier e.g. join(12..45,89..121)
537    *
538    * @param loc
539    */
 
540  4 toggle public void setEnaLocation(String loc)
541    {
542  4 setValue(LOCATION, loc);
543    }
544   
545    /**
546    * Gets the 'raw' ENA format location specifier e.g. join(12..45,89..121)
547    *
548    * @param loc
549    */
 
550  0 toggle public String getEnaLocation()
551    {
552  0 return (String) getValue(LOCATION);
553    }
554   
555    /**
556    * Readable representation, for debug only, not guaranteed not to change
557    * between versions
558    */
 
559  4347 toggle @Override
560    public String toString()
561    {
562  4347 return String.format("%d %d %s %s", getBegin(), getEnd(), getType(),
563    getDescription());
564    }
565   
566    /**
567    * Overridden to ensure that whenever two objects are equal, they have the
568    * same hashCode
569    */
 
570  4 toggle @Override
571    public int hashCode()
572    {
573  4 String s = getType() + getDescription() + getFeatureGroup()
574    + getValue("ID") + getValue("Name") + getValue("Parent")
575    + getPhase();
576  4 return s.hashCode() + getBegin() + getEnd() + (int) getScore()
577    + getStrand();
578    }
579   
580    /**
581    * Answers true if the feature's start/end values represent two related
582    * positions, rather than ends of a range. Such features may be visualised or
583    * reported differently to features on a range.
584    */
 
585  135998 toggle @Override
586    public boolean isContactFeature()
587    {
588  135998 return contactFeature;
589    }
590   
591    /**
592    * Answers true if the sequence has zero start and end position
593    *
594    * @return
595    */
 
596  172765 toggle public boolean isNonPositional()
597    {
598  172765 return begin == 0 && end == 0;
599    }
600   
601    /**
602    * Answers an html-formatted report of feature details
603    *
604    * @return
605    */
 
606  4 toggle public String getDetailsReport()
607    {
608  4 FeatureSourceI metadata = FeatureSources.getInstance()
609    .getSource(source);
610   
611  4 StringBuilder sb = new StringBuilder(128);
612  4 sb.append("<br>");
613  4 sb.append("<table>");
614  4 sb.append(String.format(ROW_DATA, "Type", type, ""));
615  4 sb.append(String.format(ROW_DATA, "Start/end", begin == end ? begin
616  3 : begin + (isContactFeature() ? ":" : "-") + end, ""));
617  4 String desc = StringUtils.stripHtmlTags(description);
618  4 sb.append(String.format(ROW_DATA, "Description", desc, ""));
619  4 if (!Float.isNaN(score) && score != 0f)
620    {
621  1 sb.append(String.format(ROW_DATA, "Score", score, ""));
622    }
623  4 if (featureGroup != null)
624    {
625  2 sb.append(String.format(ROW_DATA, "Group", featureGroup, ""));
626    }
627   
628  4 if (otherDetails != null)
629    {
630  1 TreeMap<String, Object> ordered = new TreeMap<>(
631    String.CASE_INSENSITIVE_ORDER);
632  1 ordered.putAll(otherDetails);
633   
634  1 for (Entry<String, Object> entry : ordered.entrySet())
635    {
636  2 String key = entry.getKey();
637  2 if (ATTRIBUTES.equals(key))
638    {
639  0 continue; // to avoid double reporting
640    }
641   
642  2 Object value = entry.getValue();
643  2 if (value instanceof Map<?, ?>)
644    {
645    /*
646    * expand values in a Map attribute across separate lines
647    * copy to a TreeMap for alphabetical ordering
648    */
649  0 Map<String, Object> values = (Map<String, Object>) value;
650  0 SortedMap<String, Object> sm = new TreeMap<>(
651    String.CASE_INSENSITIVE_ORDER);
652  0 sm.putAll(values);
653  0 for (Entry<?, ?> e : sm.entrySet())
654    {
655  0 sb.append(String.format(ROW_DATA, key, e.getKey().toString(), e
656    .getValue().toString()));
657    }
658    }
659    else
660    {
661    // tried <td title="key"> but it failed to provide a tooltip :-(
662  2 String attDesc = null;
663  2 if (metadata != null)
664    {
665  0 attDesc = metadata.getAttributeName(key);
666    }
667  2 String s = entry.getValue().toString();
668  2 if (isValueInteresting(key, s, metadata))
669    {
670  2 sb.append(String.format(ROW_DATA, key, attDesc == null ? ""
671    : attDesc, s));
672    }
673    }
674    }
675    }
676  4 sb.append("</table>");
677   
678  4 String text = sb.toString();
679  4 return text;
680    }
681   
682    /**
683    * Answers true if we judge the value is worth displaying, by some heuristic
684    * rules, else false
685    *
686    * @param key
687    * @param value
688    * @param metadata
689    * @return
690    */
 
691  2 toggle boolean isValueInteresting(String key, String value,
692    FeatureSourceI metadata)
693    {
694    /*
695    * currently suppressing zero values as well as null or empty
696    */
697  2 if (value == null || "".equals(value) || ".".equals(value)
698    || "0".equals(value))
699    {
700  0 return false;
701    }
702   
703  2 if (metadata == null)
704    {
705  2 return true;
706    }
707   
708  0 FeatureAttributeType attType = metadata.getAttributeType(key);
709  0 if (attType != null
710    && (attType == FeatureAttributeType.Float || attType
711    .equals(FeatureAttributeType.Integer)))
712    {
713  0 try
714    {
715  0 float fval = Float.valueOf(value);
716  0 if (fval == 0f)
717    {
718  0 return false;
719    }
720    } catch (NumberFormatException e)
721    {
722    // ignore
723    }
724    }
725   
726  0 return true; // default to interesting
727    }
728   
729    /**
730    * Sets the feature source identifier
731    *
732    * @param theSource
733    */
 
734  43 toggle public void setSource(String theSource)
735    {
736  43 source = theSource;
737    }
738    }