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 && couldBeNumber(value) || 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 * This quick check will save significant time avoiding numerous NumberFormatExceptions.
232 public boolean couldBeNumber(String f)
234 int len = f.length();
237 char ch = f.charAt(0);
244 return (ch <= '9' && ch >= '0');
248 * Answers true if at least one attribute is known for the given feature type,
254 public boolean hasAttributes(String featureType)
256 if (attributes.containsKey(featureType))
258 if (!attributes.get(featureType).isEmpty())
267 * Records the given attribute name and description for the given feature
268 * type, and updates the min-max for any numeric value
275 public void addAttribute(String featureType, String description,
276 Object value, String... attName)
278 if (featureType == null || attName == null)
284 * if attribute value is a map, drill down one more level to
285 * record its sub-fields
287 if (value instanceof Map<?, ?>)
289 for (Entry<?, ?> entry : ((Map<?, ?>) value).entrySet())
291 String[] attNames = new String[attName.length + 1];
292 System.arraycopy(attName, 0, attNames, 0, attName.length);
293 attNames[attName.length] = entry.getKey().toString();
294 addAttribute(featureType, description, entry.getValue(), attNames);
299 String valueAsString = value.toString();
300 Map<String[], AttributeData> atts = attributes.get(featureType);
303 atts = new TreeMap<>(comparator);
304 attributes.put(featureType, atts);
306 AttributeData attData = atts.get(attName);
309 attData = new AttributeData();
310 atts.put(attName, attData);
312 attData.addInstance(description, valueAsString);
316 * Answers the description of the given attribute for the given feature type,
317 * if known and unique, else null
323 public String getDescription(String featureType, String... attName)
326 Map<String[], AttributeData> atts = attributes.get(featureType);
329 AttributeData attData = atts.get(attName);
332 desc = attData.getDescription();
339 * Answers the [min, max] value range of the given attribute for the given
340 * feature type, if known, else null. Attributes with a mixture of text and
341 * numeric values are considered text (do not return a min-max range).
347 public float[] getMinMax(String featureType, String... attName)
349 Map<String[], AttributeData> atts = attributes.get(featureType);
352 AttributeData attData = atts.get(attName);
353 if (attData != null && attData.hasValue)
355 return new float[] { attData.min, attData.max };
362 * Records the given attribute description for the given feature type
368 public void addDescription(String featureType, String description,
371 if (featureType == null || attName == null)
376 Map<String[], AttributeData> atts = attributes.get(featureType);
379 atts = new TreeMap<>(comparator);
380 attributes.put(featureType, atts);
382 AttributeData attData = atts.get(attName);
385 attData = new AttributeData();
386 atts.put(attName, attData);
388 attData.addDescription(description);
392 * Answers the datatype of the feature, which is one of Character, Number or
393 * Mixed (or null if not known), as discovered from values recorded.
399 public Datatype getDatatype(String featureType, String... attName)
401 Map<String[], AttributeData> atts = attributes.get(featureType);
404 AttributeData attData = atts.get(attName);
407 return attData.getType();
414 * Resets all attribute metadata
422 * Resets attribute metadata for one feature type
426 public void clear(String featureType)
428 Map<String[], AttributeData> map = attributes.get(featureType);