Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 14:43:25 GMT
  2. Package jalview.datamodel.features

File FeatureAttributes.java

 

Coverage histogram

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

Code metrics

68
112
17
3
458
270
64
0.57
6.59
5.67
3.76

Classes

Class Line # Actions
FeatureAttributes 38 83 43
0.7957746479.6%
FeatureAttributes.Datatype 40 0 0
-1.0 -
FeatureAttributes.AttributeData 95 29 21
0.9636363496.4%
 

Contributing tests

This file is covered by 119 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.features;
22   
23    import java.util.ArrayList;
24    import java.util.Collections;
25    import java.util.Comparator;
26    import java.util.HashMap;
27    import java.util.List;
28    import java.util.Map;
29    import java.util.Map.Entry;
30    import java.util.TreeMap;
31   
32    import jalview.bin.ApplicationSingletonProvider;
33    import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
34   
35    /**
36    * A singleton class to hold the set of attributes known for each feature type
37    */
 
38    public class FeatureAttributes implements ApplicationSingletonI
39    {
 
40    public enum Datatype
41    {
42    Character, Number, Mixed
43    }
44   
 
45  545855 toggle public static FeatureAttributes getInstance()
46    {
47  545855 return ApplicationSingletonProvider.getInstance(FeatureAttributes.class);
48    }
49   
 
50  6 toggle private FeatureAttributes()
51    {
52  6 attributes = new HashMap<>();
53    }
54   
55    /*
56    * map, by feature type, of a map, by attribute name, of
57    * attribute description and min-max range (if known)
58    */
59    private Map<String, Map<String[], AttributeData>> attributes;
60   
61    /*
62    * a case-insensitive comparator so that attributes are ordered e.g.
63    * AC
64    * af
65    * CSQ:AFR_MAF
66    * CSQ:Allele
67    */
68    private Comparator<String[]> comparator = new Comparator<String[]>()
69    {
 
70  1182645 toggle @Override
71    public int compare(String[] o1, String[] o2)
72    {
73  1182645 int i = 0;
74  1728964 while (i < o1.length || i < o2.length)
75    {
76  1183133 if (o2.length <= i)
77    {
78  3 return o1.length <= i ? 0 : 1;
79    }
80  1183130 if (o1.length <= i)
81    {
82  1 return -1;
83    }
84  1183129 int comp = String.CASE_INSENSITIVE_ORDER.compare(o1[i], o2[i]);
85  1183129 if (comp != 0)
86    {
87  636810 return comp;
88    }
89  546319 i++;
90    }
91  545831 return 0; // same length and all matched
92    }
93    };
94   
 
95    private class AttributeData
96    {
97    /*
98    * description(s) for this attribute, if known
99    * (different feature source might have differing descriptions)
100    */
101    List<String> description;
102   
103    /*
104    * minimum value (of any numeric values recorded)
105    */
106    float min = 0f;
107   
108    /*
109    * maximum value (of any numeric values recorded)
110    */
111    float max = 0f;
112   
113    /*
114    * flag is set true if any numeric value is detected for this attribute
115    */
116    boolean hasValue = false;
117   
118    Datatype type;
119   
120    /**
121    * Note one instance of this attribute, recording unique, non-null
122    * descriptions, and the min/max of any numerical values
123    *
124    * @param desc
125    * @param value
126    */
 
127  545956 toggle void addInstance(String desc, String value)
128    {
129  545956 addDescription(desc);
130   
131  545956 if (value != null)
132    {
133  545956 value = value.trim();
134  545956 if (value.isEmpty())
135    {
136  977 return;
137    }
138   
139    /*
140    * Parse numeric value unless we have previously
141    * seen text data for this attribute type
142    */
143  544979 if ((type == null && couldBeNumber(value))
144    || type == Datatype.Number)
145    {
146  696 try
147    {
148  696 float f = Float.valueOf(value);
149  691 min = hasValue ? Math.min(min, f) : f;
150  691 max = hasValue ? Math.max(max, f) : f;
151  691 hasValue = true;
152  691 type = (type == null || type == Datatype.Number)
153    ? Datatype.Number
154    : Datatype.Mixed;
155    } catch (NumberFormatException e)
156    {
157    /*
158    * non-numeric data: treat attribute as Character (or Mixed)
159    */
160  5 type = (type == null || type == Datatype.Character)
161    ? Datatype.Character
162    : Datatype.Mixed;
163  5 min = 0f;
164  5 max = 0f;
165  5 hasValue = false;
166    }
167    }
168    else
169    {
170    /*
171    * if not a number, and not seen before...
172    */
173  544283 type = Datatype.Character;
174  544283 min = 0f;
175  544283 max = 0f;
176  544283 hasValue = false;
177    }
178    }
179    }
180   
181    /**
182    * Answers the description of the attribute, if recorded and unique, or null
183    * if either no, or more than description is recorded
184    *
185    * @return
186    */
 
187  2 toggle public String getDescription()
188    {
189  2 if (description != null && description.size() == 1)
190    {
191  1 return description.get(0);
192    }
193  1 return null;
194    }
195   
 
196  4 toggle public Datatype getType()
197    {
198  4 return type;
199    }
200   
201    /**
202    * Adds the given description to the list of known descriptions (without
203    * duplication)
204    *
205    * @param desc
206    */
 
207  545958 toggle public void addDescription(String desc)
208    {
209  545958 if (desc != null)
210    {
211  212 if (description == null)
212    {
213  39 description = new ArrayList<>();
214    }
215  212 if (!description.contains(desc))
216    {
217  40 description.add(desc);
218    }
219    }
220    }
221    }
222   
223    /**
224    * Answers the attribute names known for the given feature type, in
225    * alphabetical order (not case sensitive), or an empty set if no attributes
226    * are known. An attribute name is typically 'simple' e.g. "AC", but may be
227    * 'compound' e.g. {"CSQ", "Allele"} where a feature has map-valued attributes
228    *
229    * @param featureType
230    * @return
231    */
 
232  0 toggle public List<String[]> getAttributes(String featureType)
233    {
234  0 if (!attributes.containsKey(featureType))
235    {
236  0 return Collections.<String[]> emptyList();
237    }
238   
239  0 return new ArrayList<>(attributes.get(featureType).keySet());
240    }
241   
242    /**
243    * A partial check that the string is numeric - only checks the first
244    * character. Returns true if the first character is a digit, or if it is '.',
245    * '+' or '-' and not the only character. Otherwise returns false (including
246    * for an empty string). Note this is not a complete check as it returns true
247    * for (e.g.) "1A".
248    *
249    * @param f
250    * @return
251    */
 
252  187 toggle public static boolean couldBeNumber(String f)
253    {
254  187 int len = f.length();
255  187 if (len == 0)
256    {
257  0 return false;
258    }
259  187 char ch = f.charAt(0);
260  187 switch (ch)
261    {
262  9 case '.':
263  12 case '+':
264  4 case '-':
265  25 return len > 1;
266    }
267  162 return (ch <= '9' && ch >= '0');
268    }
269   
270    /**
271    * Answers true if at least one attribute is known for the given feature type,
272    * else false
273    *
274    * @param featureType
275    * @return
276    */
 
277  0 toggle public boolean hasAttributes(String featureType)
278    {
279  0 if (attributes.containsKey(featureType))
280    {
281  0 if (!attributes.get(featureType).isEmpty())
282    {
283  0 return true;
284    }
285    }
286  0 return false;
287    }
288   
289    /**
290    * Records the given attribute name and description for the given feature
291    * type, and updates the min-max for any numeric value
292    *
293    * @param featureType
294    * @param description
295    * @param value
296    * @param attName
297    */
 
298  546017 toggle public void addAttribute(String featureType, String description,
299    Object value, String... attName)
300    {
301  546017 if (featureType == null || attName == null)
302    {
303  0 return;
304    }
305   
306    /*
307    * if attribute value is a map, drill down one more level to
308    * record its sub-fields
309    */
310  546017 if (value instanceof Map<?, ?>)
311    {
312  61 for (Entry<?, ?> entry : ((Map<?, ?>) value).entrySet())
313    {
314  175 String[] attNames = new String[attName.length + 1];
315  175 System.arraycopy(attName, 0, attNames, 0, attName.length);
316  175 attNames[attName.length] = entry.getKey().toString();
317  175 addAttribute(featureType, description, entry.getValue(), attNames);
318    }
319  61 return;
320    }
321   
322  545956 String valueAsString = value.toString();
323  545956 Map<String[], AttributeData> atts = attributes.get(featureType);
324  545956 if (atts == null)
325    {
326  49 atts = new TreeMap<>(comparator);
327  49 attributes.put(featureType, atts);
328    }
329  545956 AttributeData attData = atts.get(attName);
330  545956 if (attData == null)
331    {
332  188 attData = new AttributeData();
333  188 atts.put(attName, attData);
334    }
335  545956 attData.addInstance(description, valueAsString);
336    }
337   
338    /**
339    * Answers the description of the given attribute for the given feature type,
340    * if known and unique, else null
341    *
342    * @param featureType
343    * @param attName
344    * @return
345    */
 
346  3 toggle public String getDescription(String featureType, String... attName)
347    {
348  3 String desc = null;
349  3 Map<String[], AttributeData> atts = attributes.get(featureType);
350  3 if (atts != null)
351    {
352  2 AttributeData attData = atts.get(attName);
353  2 if (attData != null)
354    {
355  2 desc = attData.getDescription();
356    }
357    }
358  3 return desc;
359    }
360   
361    /**
362    * Answers the [min, max] value range of the given attribute for the given
363    * feature type, if known, else null. Attributes with a mixture of text and
364    * numeric values are considered text (do not return a min-max range).
365    *
366    * @param featureType
367    * @param attName
368    * @return
369    */
 
370  7 toggle public float[] getMinMax(String featureType, String... attName)
371    {
372  7 Map<String[], AttributeData> atts = attributes.get(featureType);
373  7 if (atts != null)
374    {
375  6 AttributeData attData = atts.get(attName);
376  6 if (attData != null && attData.hasValue)
377    {
378  4 return new float[] { attData.min, attData.max };
379    }
380    }
381  3 return null;
382    }
383   
384    /**
385    * Records the given attribute description for the given feature type
386    *
387    * @param featureType
388    * @param attName
389    * @param description
390    */
 
391  2 toggle public void addDescription(String featureType, String description,
392    String... attName)
393    {
394  2 if (featureType == null || attName == null)
395    {
396  0 return;
397    }
398   
399  2 Map<String[], AttributeData> atts = attributes.get(featureType);
400  2 if (atts == null)
401    {
402  1 atts = new TreeMap<>(comparator);
403  1 attributes.put(featureType, atts);
404    }
405  2 AttributeData attData = atts.get(attName);
406  2 if (attData == null)
407    {
408  1 attData = new AttributeData();
409  1 atts.put(attName, attData);
410    }
411  2 attData.addDescription(description);
412    }
413   
414    /**
415    * Answers the datatype of the feature, which is one of Character, Number or
416    * Mixed (or null if not known), as discovered from values recorded.
417    *
418    * @param featureType
419    * @param attName
420    * @return
421    */
 
422  6 toggle public Datatype getDatatype(String featureType, String... attName)
423    {
424  6 Map<String[], AttributeData> atts = attributes.get(featureType);
425  6 if (atts != null)
426    {
427  5 AttributeData attData = atts.get(attName);
428  5 if (attData != null)
429    {
430  4 return attData.getType();
431    }
432    }
433  2 return null;
434    }
435   
436    /**
437    * Resets all attribute metadata
438    */
 
439  4 toggle public void clear()
440    {
441  4 attributes.clear();
442    }
443   
444    /**
445    * Resets attribute metadata for one feature type
446    *
447    * @param featureType
448    */
 
449  0 toggle public void clear(String featureType)
450    {
451  0 Map<String[], AttributeData> map = attributes.get(featureType);
452  0 if (map != null)
453    {
454  0 map.clear();
455    }
456   
457    }
458    }