1 package jalview.datamodel.features;
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.Comparator;
6 import java.util.HashMap;
9 import java.util.Map.Entry;
10 import java.util.TreeMap;
13 * A singleton class to hold the set of attributes known for each feature type
15 public class FeatureAttributes
17 private static FeatureAttributes instance = new FeatureAttributes();
20 * map, by feature type, of a map, by attribute name, of
21 * attribute description and min-max range (if known)
23 private Map<String, Map<String[], AttributeData>> attributes;
26 * a case-insensitive comparator so that attributes are ordered e.g.
32 private Comparator<String[]> comparator = new Comparator<String[]>()
35 public int compare(String[] o1, String[] o2)
38 while (i < o1.length || i < o2.length)
42 return o1.length <= i ? 0 : 1;
48 int comp = String.CASE_INSENSITIVE_ORDER.compare(o1[i], o2[i]);
55 return 0; // same length and all matched
59 private class AttributeData
62 * description(s) for this attribute, if known
63 * (different feature source might have differing descriptions)
65 List<String> description;
68 * minimum value (of any numeric values recorded)
73 * maximum value (of any numeric values recorded)
78 * flag is set true if any numeric value is detected for this attribute
80 boolean hasValue = false;
83 * Note one instance of this attribute, recording unique, non-null names,
84 * and the min/max of any numerical values
89 void addInstance(String desc, String value)
97 float f = Float.valueOf(value);
98 min = Float.min(min, f);
99 max = Float.max(max, f);
101 } catch (NumberFormatException e)
103 // ok, wasn't a number, ignore for min-max purposes
109 * Answers the description of the attribute, if recorded and unique, or null if either no, or more than description is recorded
112 public String getDescription()
114 if (description != null && description.size() == 1)
116 return description.get(0);
122 * Adds the given description to the list of known descriptions (without
127 public void addDescription(String desc)
131 if (description == null)
133 description = new ArrayList<>();
135 if (!description.contains(desc))
137 description.add(desc);
144 * Answers the singleton instance of this class
148 public static FeatureAttributes getInstance()
153 private FeatureAttributes()
155 attributes = new HashMap<>();
159 * Answers the attribute names known for the given feature type, in
160 * alphabetical order (not case sensitive), or an empty set if no attributes
161 * are known. An attribute name is typically 'simple' e.g. "AC", but may be
162 * 'compound' e.g. {"CSQ", "Allele"} where a feature has map-valued attributes
167 public List<String[]> getAttributes(String featureType)
169 if (!attributes.containsKey(featureType))
171 return Collections.<String[]> emptyList();
174 return new ArrayList<>(attributes.get(featureType).keySet());
178 * Answers true if at least one attribute is known for the given feature type,
184 public boolean hasAttributes(String featureType)
186 if (attributes.containsKey(featureType))
188 if (!attributes.get(featureType).isEmpty())
197 * Records the given attribute name and description for the given feature
198 * type, and updates the min-max for any numeric value
205 public void addAttribute(String featureType, String description,
206 Object value, String... attName)
208 if (featureType == null || attName == null)
214 * if attribute value is a map, drill down one more level to
215 * record its sub-fields
217 if (value instanceof Map<?, ?>)
219 for (Entry<?, ?> entry : ((Map<?, ?>) value).entrySet())
221 String[] attNames = new String[attName.length + 1];
222 System.arraycopy(attName, 0, attNames, 0, attName.length);
223 attNames[attName.length] = entry.getKey().toString();
224 addAttribute(featureType, description, entry.getValue(), attNames);
229 String valueAsString = value.toString();
230 Map<String[], AttributeData> atts = attributes.get(featureType);
233 atts = new TreeMap<>(comparator);
234 attributes.put(featureType, atts);
236 AttributeData attData = atts.get(attName);
239 attData = new AttributeData();
240 atts.put(attName, attData);
242 attData.addInstance(description, valueAsString);
246 * Answers the description of the given attribute for the given feature type,
247 * if known and unique, else null
253 public String getDescription(String featureType, String... attName)
256 Map<String[], AttributeData> atts = attributes.get(featureType);
259 AttributeData attData = atts.get(attName);
262 desc = attData.getDescription();
269 * Answers the [min, max] value range of the given attribute for the given
270 * feature type, if known, else null. Attributes which only have text values
271 * would normally return null, however text values which happen to be numeric
272 * could result in a 'min-max' range.
278 public float[] getMinMax(String featureType, String... attName)
280 Map<String[], AttributeData> atts = attributes.get(featureType);
283 AttributeData attData = atts.get(attName);
284 if (attData != null && attData.hasValue)
286 return new float[] { attData.min, attData.max };
293 * Records the given attribute description for the given feature type
299 public void addDescription(String featureType, String description,
302 if (featureType == null || attName == null)
307 Map<String[], AttributeData> atts = attributes.get(featureType);
310 atts = new TreeMap<>(comparator);
311 attributes.put(featureType, atts);
313 AttributeData attData = atts.get(attName);
316 attData = new AttributeData();
317 atts.put(attName, attData);
319 attData.addDescription(description);