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();
137 * Parse numeric value unless we have previously
138 * seen text data for this attribute type
140 if (type == null || type == Datatype.Number)
144 float f = Float.valueOf(value);
145 min = hasValue ? Math.min(min, f) : f;
146 max = hasValue ? Math.max(max, f) : f;
148 type = (type == null || type == Datatype.Number)
151 } catch (NumberFormatException e)
154 * non-numeric data: treat attribute as Character (or Mixed)
156 type = (type == null || type == Datatype.Character)
168 * Answers the description of the attribute, if recorded and unique, or null if either no, or more than description is recorded
171 public String getDescription()
173 if (description != null && description.size() == 1)
175 return description.get(0);
180 public Datatype getType()
186 * Adds the given description to the list of known descriptions (without
191 public void addDescription(String desc)
195 if (description == null)
197 description = new ArrayList<>();
199 if (!description.contains(desc))
201 description.add(desc);
208 * Answers the attribute names known for the given feature type, in
209 * alphabetical order (not case sensitive), or an empty set if no attributes
210 * are known. An attribute name is typically 'simple' e.g. "AC", but may be
211 * 'compound' e.g. {"CSQ", "Allele"} where a feature has map-valued attributes
216 public List<String[]> getAttributes(String featureType)
218 if (!attributes.containsKey(featureType))
220 return Collections.<String[]> emptyList();
223 return new ArrayList<>(attributes.get(featureType).keySet());
227 * Answers true if at least one attribute is known for the given feature type,
233 public boolean hasAttributes(String featureType)
235 if (attributes.containsKey(featureType))
237 if (!attributes.get(featureType).isEmpty())
246 * Records the given attribute name and description for the given feature
247 * type, and updates the min-max for any numeric value
254 public void addAttribute(String featureType, String description,
255 Object value, String... attName)
257 if (featureType == null || attName == null)
263 * if attribute value is a map, drill down one more level to
264 * record its sub-fields
266 if (value instanceof Map<?, ?>)
268 for (Entry<?, ?> entry : ((Map<?, ?>) value).entrySet())
270 String[] attNames = new String[attName.length + 1];
271 System.arraycopy(attName, 0, attNames, 0, attName.length);
272 attNames[attName.length] = entry.getKey().toString();
273 addAttribute(featureType, description, entry.getValue(), attNames);
278 String valueAsString = value.toString();
279 Map<String[], AttributeData> atts = attributes.get(featureType);
282 atts = new TreeMap<>(comparator);
283 attributes.put(featureType, atts);
285 AttributeData attData = atts.get(attName);
288 attData = new AttributeData();
289 atts.put(attName, attData);
291 attData.addInstance(description, valueAsString);
295 * Answers the description of the given attribute for the given feature type,
296 * if known and unique, else null
302 public String getDescription(String featureType, String... attName)
305 Map<String[], AttributeData> atts = attributes.get(featureType);
308 AttributeData attData = atts.get(attName);
311 desc = attData.getDescription();
318 * Answers the [min, max] value range of the given attribute for the given
319 * feature type, if known, else null. Attributes with a mixture of text and
320 * numeric values are considered text (do not return a min-max range).
326 public float[] getMinMax(String featureType, String... attName)
328 Map<String[], AttributeData> atts = attributes.get(featureType);
331 AttributeData attData = atts.get(attName);
332 if (attData != null && attData.hasValue)
334 return new float[] { attData.min, attData.max };
341 * Records the given attribute description for the given feature type
347 public void addDescription(String featureType, String description,
350 if (featureType == null || attName == null)
355 Map<String[], AttributeData> atts = attributes.get(featureType);
358 atts = new TreeMap<>(comparator);
359 attributes.put(featureType, atts);
361 AttributeData attData = atts.get(attName);
364 attData = new AttributeData();
365 atts.put(attName, attData);
367 attData.addDescription(description);
371 * Answers the datatype of the feature, which is one of Character, Number or
372 * Mixed (or null if not known), as discovered from values recorded.
378 public Datatype getDatatype(String featureType, String... attName)
380 Map<String[], AttributeData> atts = attributes.get(featureType);
383 AttributeData attData = atts.get(attName);
386 return attData.getType();
393 * Resets all attribute metadata
401 * Resets attribute metadata for one feature type
405 public void clear(String featureType)
407 Map<String[], AttributeData> map = attributes.get(featureType);