1 package jalview.datamodel.features;
3 import jalview.bin.ApplicationSingletonProvider;
4 import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
6 import java.util.ArrayList;
7 import java.util.Collections;
8 import java.util.Comparator;
9 import java.util.HashMap;
10 import java.util.List;
12 import java.util.Map.Entry;
13 import java.util.TreeMap;
16 * A singleton class to hold the set of attributes known for each feature type
18 public class FeatureAttributes implements ApplicationSingletonI
22 Character, Number, Mixed
25 public static FeatureAttributes getInstance()
27 return (FeatureAttributes) ApplicationSingletonProvider
28 .getInstance(FeatureAttributes.class);
31 private FeatureAttributes()
33 attributes = new HashMap<>();
37 * map, by feature type, of a map, by attribute name, of
38 * attribute description and min-max range (if known)
40 private Map<String, Map<String[], AttributeData>> attributes;
43 * a case-insensitive comparator so that attributes are ordered e.g.
49 private Comparator<String[]> comparator = new Comparator<String[]>()
52 public int compare(String[] o1, String[] o2)
55 while (i < o1.length || i < o2.length)
59 return o1.length <= i ? 0 : 1;
65 int comp = String.CASE_INSENSITIVE_ORDER.compare(o1[i], o2[i]);
72 return 0; // same length and all matched
76 private class AttributeData
79 * description(s) for this attribute, if known
80 * (different feature source might have differing descriptions)
82 List<String> description;
85 * minimum value (of any numeric values recorded)
90 * maximum value (of any numeric values recorded)
95 * flag is set true if any numeric value is detected for this attribute
97 boolean hasValue = false;
102 * Note one instance of this attribute, recording unique, non-null
103 * descriptions, and the min/max of any numerical values
108 void addInstance(String desc, String value)
110 addDescription(desc);
114 value = value.trim();
117 * Parse numeric value unless we have previously
118 * seen text data for this attribute type
120 if (type == null || type == Datatype.Number)
124 float f = Float.valueOf(value);
125 min = hasValue ? Math.min(min, f) : f;
126 max = hasValue ? Math.max(max, f) : f;
128 type = (type == null || type == Datatype.Number)
131 } catch (NumberFormatException e)
134 * non-numeric data: treat attribute as Character (or Mixed)
136 type = (type == null || type == Datatype.Character)
148 * Answers the description of the attribute, if recorded and unique, or null if either no, or more than description is recorded
151 public String getDescription()
153 if (description != null && description.size() == 1)
155 return description.get(0);
160 public Datatype getType()
166 * Adds the given description to the list of known descriptions (without
171 public void addDescription(String desc)
175 if (description == null)
177 description = new ArrayList<>();
179 if (!description.contains(desc))
181 description.add(desc);
188 * Answers the attribute names known for the given feature type, in
189 * alphabetical order (not case sensitive), or an empty set if no attributes
190 * are known. An attribute name is typically 'simple' e.g. "AC", but may be
191 * 'compound' e.g. {"CSQ", "Allele"} where a feature has map-valued attributes
196 public List<String[]> getAttributes(String featureType)
198 if (!attributes.containsKey(featureType))
200 return Collections.<String[]> emptyList();
203 return new ArrayList<>(attributes.get(featureType).keySet());
207 * Answers true if at least one attribute is known for the given feature type,
213 public boolean hasAttributes(String featureType)
215 if (attributes.containsKey(featureType))
217 if (!attributes.get(featureType).isEmpty())
226 * Records the given attribute name and description for the given feature
227 * type, and updates the min-max for any numeric value
234 public void addAttribute(String featureType, String description,
235 Object value, String... attName)
237 if (featureType == null || attName == null)
243 * if attribute value is a map, drill down one more level to
244 * record its sub-fields
246 if (value instanceof Map<?, ?>)
248 for (Entry<?, ?> entry : ((Map<?, ?>) value).entrySet())
250 String[] attNames = new String[attName.length + 1];
251 System.arraycopy(attName, 0, attNames, 0, attName.length);
252 attNames[attName.length] = entry.getKey().toString();
253 addAttribute(featureType, description, entry.getValue(), attNames);
258 String valueAsString = value.toString();
259 Map<String[], AttributeData> atts = attributes.get(featureType);
262 atts = new TreeMap<>(comparator);
263 attributes.put(featureType, atts);
265 AttributeData attData = atts.get(attName);
268 attData = new AttributeData();
269 atts.put(attName, attData);
271 attData.addInstance(description, valueAsString);
275 * Answers the description of the given attribute for the given feature type,
276 * if known and unique, else null
282 public String getDescription(String featureType, String... attName)
285 Map<String[], AttributeData> atts = attributes.get(featureType);
288 AttributeData attData = atts.get(attName);
291 desc = attData.getDescription();
298 * Answers the [min, max] value range of the given attribute for the given
299 * feature type, if known, else null. Attributes with a mixture of text and
300 * numeric values are considered text (do not return a min-max range).
306 public float[] getMinMax(String featureType, String... attName)
308 Map<String[], AttributeData> atts = attributes.get(featureType);
311 AttributeData attData = atts.get(attName);
312 if (attData != null && attData.hasValue)
314 return new float[] { attData.min, attData.max };
321 * Records the given attribute description for the given feature type
327 public void addDescription(String featureType, String description,
330 if (featureType == null || attName == null)
335 Map<String[], AttributeData> atts = attributes.get(featureType);
338 atts = new TreeMap<>(comparator);
339 attributes.put(featureType, atts);
341 AttributeData attData = atts.get(attName);
344 attData = new AttributeData();
345 atts.put(attName, attData);
347 attData.addDescription(description);
351 * Answers the datatype of the feature, which is one of Character, Number or
352 * Mixed (or null if not known), as discovered from values recorded.
358 public Datatype getDatatype(String featureType, String... attName)
360 Map<String[], AttributeData> atts = attributes.get(featureType);
363 AttributeData attData = atts.get(attName);
366 return attData.getType();