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.features;
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;
29 import java.util.Map.Entry;
30 import java.util.TreeMap;
32 import jalview.bin.ApplicationSingletonProvider;
33 import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
36 * A singleton class to hold the set of attributes known for each feature type
38 public class FeatureAttributes implements ApplicationSingletonI
42 Character, Number, Mixed
45 public static FeatureAttributes getInstance()
47 return (FeatureAttributes) ApplicationSingletonProvider
48 .getInstance(FeatureAttributes.class);
51 private FeatureAttributes()
53 attributes = new HashMap<>();
57 * map, by feature type, of a map, by attribute name, of
58 * attribute description and min-max range (if known)
60 private Map<String, Map<String[], AttributeData>> attributes;
63 * a case-insensitive comparator so that attributes are ordered e.g.
69 private Comparator<String[]> comparator = new Comparator<String[]>()
72 public int compare(String[] o1, String[] o2)
75 while (i < o1.length || i < o2.length)
79 return o1.length <= i ? 0 : 1;
85 int comp = String.CASE_INSENSITIVE_ORDER.compare(o1[i], o2[i]);
92 return 0; // same length and all matched
96 private class AttributeData
99 * description(s) for this attribute, if known
100 * (different feature source might have differing descriptions)
102 List<String> description;
105 * minimum value (of any numeric values recorded)
110 * maximum value (of any numeric values recorded)
115 * flag is set true if any numeric value is detected for this attribute
117 boolean hasValue = false;
122 * Note one instance of this attribute, recording unique, non-null
123 * descriptions, and the min/max of any numerical values
128 void addInstance(String desc, String value)
130 addDescription(desc);
134 value = value.trim();
141 * Parse numeric value unless we have previously
142 * seen text data for this attribute type
144 if ((type == null && couldBeNumber(value))
145 || type == Datatype.Number)
149 float f = Float.valueOf(value);
150 min = hasValue ? Math.min(min, f) : f;
151 max = hasValue ? Math.max(max, f) : f;
153 type = (type == null || type == Datatype.Number)
156 } catch (NumberFormatException e)
159 * non-numeric data: treat attribute as Character (or Mixed)
161 type = (type == null || type == Datatype.Character)
172 * if not a number, and not seen before...
174 type = Datatype.Character;
183 * Answers the description of the attribute, if recorded and unique, or null
184 * if either no, or more than description is recorded
188 public String getDescription()
190 if (description != null && description.size() == 1)
192 return description.get(0);
197 public Datatype getType()
203 * Adds the given description to the list of known descriptions (without
208 public void addDescription(String desc)
212 if (description == null)
214 description = new ArrayList<>();
216 if (!description.contains(desc))
218 description.add(desc);
225 * Answers the attribute names known for the given feature type, in
226 * alphabetical order (not case sensitive), or an empty set if no attributes
227 * are known. An attribute name is typically 'simple' e.g. "AC", but may be
228 * 'compound' e.g. {"CSQ", "Allele"} where a feature has map-valued attributes
233 public List<String[]> getAttributes(String featureType)
235 if (!attributes.containsKey(featureType))
237 return Collections.<String[]> emptyList();
240 return new ArrayList<>(attributes.get(featureType).keySet());
244 * A partial check that the string is numeric - only checks the first
245 * character. Returns true if the first character is a digit, or if it is '.',
246 * '+' or '-' and not the only character. Otherwise returns false (including
247 * for an empty string). Note this is not a complete check as it returns true
253 public static boolean couldBeNumber(String f)
255 int len = f.length();
260 char ch = f.charAt(0);
268 return (ch <= '9' && ch >= '0');
272 * Answers true if at least one attribute is known for the given feature type,
278 public boolean hasAttributes(String featureType)
280 if (attributes.containsKey(featureType))
282 if (!attributes.get(featureType).isEmpty())
291 * Records the given attribute name and description for the given feature
292 * type, and updates the min-max for any numeric value
299 public void addAttribute(String featureType, String description,
300 Object value, String... attName)
302 if (featureType == null || attName == null)
308 * if attribute value is a map, drill down one more level to
309 * record its sub-fields
311 if (value instanceof Map<?, ?>)
313 for (Entry<?, ?> entry : ((Map<?, ?>) value).entrySet())
315 String[] attNames = new String[attName.length + 1];
316 System.arraycopy(attName, 0, attNames, 0, attName.length);
317 attNames[attName.length] = entry.getKey().toString();
318 addAttribute(featureType, description, entry.getValue(), attNames);
323 String valueAsString = value.toString();
324 Map<String[], AttributeData> atts = attributes.get(featureType);
327 atts = new TreeMap<>(comparator);
328 attributes.put(featureType, atts);
330 AttributeData attData = atts.get(attName);
333 attData = new AttributeData();
334 atts.put(attName, attData);
336 attData.addInstance(description, valueAsString);
340 * Answers the description of the given attribute for the given feature type,
341 * if known and unique, else null
347 public String getDescription(String featureType, String... attName)
350 Map<String[], AttributeData> atts = attributes.get(featureType);
353 AttributeData attData = atts.get(attName);
356 desc = attData.getDescription();
363 * Answers the [min, max] value range of the given attribute for the given
364 * feature type, if known, else null. Attributes with a mixture of text and
365 * numeric values are considered text (do not return a min-max range).
371 public float[] getMinMax(String featureType, String... attName)
373 Map<String[], AttributeData> atts = attributes.get(featureType);
376 AttributeData attData = atts.get(attName);
377 if (attData != null && attData.hasValue)
379 return new float[] { attData.min, attData.max };
386 * Records the given attribute description for the given feature type
392 public void addDescription(String featureType, String description,
395 if (featureType == null || attName == null)
400 Map<String[], AttributeData> atts = attributes.get(featureType);
403 atts = new TreeMap<>(comparator);
404 attributes.put(featureType, atts);
406 AttributeData attData = atts.get(attName);
409 attData = new AttributeData();
410 atts.put(attName, attData);
412 attData.addDescription(description);
416 * Answers the datatype of the feature, which is one of Character, Number or
417 * Mixed (or null if not known), as discovered from values recorded.
423 public Datatype getDatatype(String featureType, String... attName)
425 Map<String[], AttributeData> atts = attributes.get(featureType);
428 AttributeData attData = atts.get(attName);
431 return attData.getType();
438 * Resets all attribute metadata
446 * Resets attribute metadata for one feature type
450 public void clear(String featureType)
452 Map<String[], AttributeData> map = attributes.get(featureType);