1 package jalview.datamodel.features;
3 import jalview.bin.Instance;
5 import java.util.ArrayList;
6 import java.util.Collections;
7 import java.util.Comparator;
8 import java.util.HashMap;
11 import java.util.Map.Entry;
12 import java.util.TreeMap;
15 * A singleton class to hold the set of attributes known for each feature type
17 public class FeatureAttributes
21 Character, Number, Mixed
24 public static FeatureAttributes getInstance()
26 Instance i = Instance.getInstance();
27 return (i.featureAttributes == null
28 ? i.featureAttributes = new FeatureAttributes()
29 : i.featureAttributes);
32 private FeatureAttributes()
34 attributes = new HashMap<>();
38 * map, by feature type, of a map, by attribute name, of
39 * attribute description and min-max range (if known)
41 private Map<String, Map<String[], AttributeData>> attributes;
44 * a case-insensitive comparator so that attributes are ordered e.g.
50 private Comparator<String[]> comparator = new Comparator<String[]>()
53 public int compare(String[] o1, String[] o2)
56 while (i < o1.length || i < o2.length)
60 return o1.length <= i ? 0 : 1;
66 int comp = String.CASE_INSENSITIVE_ORDER.compare(o1[i], o2[i]);
73 return 0; // same length and all matched
77 private class AttributeData
80 * description(s) for this attribute, if known
81 * (different feature source might have differing descriptions)
83 List<String> description;
86 * minimum value (of any numeric values recorded)
91 * maximum value (of any numeric values recorded)
96 * flag is set true if any numeric value is detected for this attribute
98 boolean hasValue = false;
103 * Note one instance of this attribute, recording unique, non-null
104 * descriptions, and the min/max of any numerical values
109 void addInstance(String desc, String value)
111 addDescription(desc);
115 value = value.trim();
118 * Parse numeric value unless we have previously
119 * seen text data for this attribute type
121 if (type == null || type == Datatype.Number)
125 float f = Float.valueOf(value);
126 min = hasValue ? Math.min(min, f) : f;
127 max = hasValue ? Math.max(max, f) : f;
129 type = (type == null || type == Datatype.Number)
132 } catch (NumberFormatException e)
135 * non-numeric data: treat attribute as Character (or Mixed)
137 type = (type == null || type == Datatype.Character)
149 * Answers the description of the attribute, if recorded and unique, or null if either no, or more than description is recorded
152 public String getDescription()
154 if (description != null && description.size() == 1)
156 return description.get(0);
161 public Datatype getType()
167 * Adds the given description to the list of known descriptions (without
172 public void addDescription(String desc)
176 if (description == null)
178 description = new ArrayList<>();
180 if (!description.contains(desc))
182 description.add(desc);
189 * Answers the attribute names known for the given feature type, in
190 * alphabetical order (not case sensitive), or an empty set if no attributes
191 * are known. An attribute name is typically 'simple' e.g. "AC", but may be
192 * 'compound' e.g. {"CSQ", "Allele"} where a feature has map-valued attributes
197 public List<String[]> getAttributes(String featureType)
199 if (!attributes.containsKey(featureType))
201 return Collections.<String[]> emptyList();
204 return new ArrayList<>(attributes.get(featureType).keySet());
208 * Answers true if at least one attribute is known for the given feature type,
214 public boolean hasAttributes(String featureType)
216 if (attributes.containsKey(featureType))
218 if (!attributes.get(featureType).isEmpty())
227 * Records the given attribute name and description for the given feature
228 * type, and updates the min-max for any numeric value
235 public void addAttribute(String featureType, String description,
236 Object value, String... attName)
238 if (featureType == null || attName == null)
244 * if attribute value is a map, drill down one more level to
245 * record its sub-fields
247 if (value instanceof Map<?, ?>)
249 for (Entry<?, ?> entry : ((Map<?, ?>) value).entrySet())
251 String[] attNames = new String[attName.length + 1];
252 System.arraycopy(attName, 0, attNames, 0, attName.length);
253 attNames[attName.length] = entry.getKey().toString();
254 addAttribute(featureType, description, entry.getValue(), attNames);
259 String valueAsString = value.toString();
260 Map<String[], AttributeData> atts = attributes.get(featureType);
263 atts = new TreeMap<>(comparator);
264 attributes.put(featureType, atts);
266 AttributeData attData = atts.get(attName);
269 attData = new AttributeData();
270 atts.put(attName, attData);
272 attData.addInstance(description, valueAsString);
276 * Answers the description of the given attribute for the given feature type,
277 * if known and unique, else null
283 public String getDescription(String featureType, String... attName)
286 Map<String[], AttributeData> atts = attributes.get(featureType);
289 AttributeData attData = atts.get(attName);
292 desc = attData.getDescription();
299 * Answers the [min, max] value range of the given attribute for the given
300 * feature type, if known, else null. Attributes with a mixture of text and
301 * numeric values are considered text (do not return a min-max range).
307 public float[] getMinMax(String featureType, String... attName)
309 Map<String[], AttributeData> atts = attributes.get(featureType);
312 AttributeData attData = atts.get(attName);
313 if (attData != null && attData.hasValue)
315 return new float[] { attData.min, attData.max };
322 * Records the given attribute description for the given feature type
328 public void addDescription(String featureType, String description,
331 if (featureType == null || attName == null)
336 Map<String[], AttributeData> atts = attributes.get(featureType);
339 atts = new TreeMap<>(comparator);
340 attributes.put(featureType, atts);
342 AttributeData attData = atts.get(attName);
345 attData = new AttributeData();
346 atts.put(attName, attData);
348 attData.addDescription(description);
352 * Answers the datatype of the feature, which is one of Character, Number or
353 * Mixed (or null if not known), as discovered from values recorded.
359 public Datatype getDatatype(String featureType, String... attName)
361 Map<String[], AttributeData> atts = attributes.get(featureType);
364 AttributeData attData = atts.get(attName);
367 return attData.getType();